package cn.tedu.justone.pack01;

/**
 * ZZ：JustOne
 * VX：Andylau2020smile
 * DY：JustOne2018
 * ZT：《》
 */
public class Demo00 {

    //刘景景自己总结：
    // 多线程，其实就是把一条队伍拆成多条队伍
    // 无锁的东西，多条队伍都可以操作
    // 上锁的东西，谁拿到了锁谁操作

    /*

    */

    //------------------------------------------------------

    //我们纵观整个并发锁的发展史，实际上一种思想的发展史
    //（1）synchronized 最常用的悲观锁，使用方便，但它是一种非公平锁。
    //（2）ReentrantLock 可重入锁，提供了公平锁等丰富特性。
    //（3）ReentrantReadWriteLock 可重入读写锁 支持读写分离，在读多的场景中表现优异。
    //（4）StampedLock的读不阻塞写，是一种更加优异的思想。

    //synchronized是在JVM层面上实现的，不但可以通过一些监控工具监控synchronized的锁定，
    // 而且在代码执行时出现异常，JVM会自动释放锁定；
    //ReentrantLock、ReentrantReadWriteLock,、StampedLock都是对象层面的锁定，
    // 要保证锁定一定会被释放，就必须将unLock()放到finally{}中；
    //StampedLock 对吞吐量有巨大的改进，特别是在读线程越来越多的场景下；
    //StampedLock有一个复杂的API，对于加锁操作，很容易误用其他方法;
    //当只有少量竞争者的时候，synchronized是一个很好的通用的锁实现;
    //当线程增长能够预估，ReentrantLock是一个很好的通用的锁实现;



//-------------------------------------------------
    //公平锁
    // 是指多个线程竞争同一资源时[等待同一个锁时]，获取资源的顺序是按照申请锁的先后顺序的；
    // 先到的线程优先获取锁，等待锁的线程不会饿死，但整体效率相对比较低。
    //

    //非公平锁
    // 是指多个线程竞争同一资源时，获取资源的顺序是不确定的，一般是抢占式的；
    // 非公平锁相对公平锁是增加了获取资源的不确定性，但是整体效率得以提升；
    // synchronized就是非公平锁。

    //自旋锁
    // 1,是指当一个线程在获取锁的时候，如果锁已经被其它线程获取，那么该线程将循环等待，
    //   然后不断的判断锁是否能够被成功获取，直到获取到锁才会退出循环。
    // 2,缺点：获取锁的线程一直处于活跃状态，但是并没有执行任何有效的任务。
    // 3,优点：自旋锁不会使线程状态发生切换，一直活跃状态而不会进入入阻塞状态，
    //   减少了不必要的上下文切换，执行速度快。
    //

    //可重入锁
    // 如果锁具备可重入性，则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁，
    // 可重入性实际上表明了锁的分配机制：基于线程的分配，而不是基于方法调用的分配。
    // 举个简单的例子，当一个线程执行到某个synchronized方法method1()时，
    // 而在method1()中会调用synchronized修饰的另外一个方法method2()，
    // 此时线程不必重新去申请锁，而是可以直接执行方法method2()。
    //
    // class Demo{ //this
    //    public synchronized void method1() {
    //        method2();
    //    }
    //    public synchronized void method2() {
    //        //........
    //    }
    // }
    // 上述代码中，假如synchronized不具备可重入性，此时线程A需要重新申请锁。
    // 但是这就会造成一个问题，因为线程A已经持有了该对象的锁，而又在申请获取该对象的锁，
    // 这样就会导致线程A一直等待着一个自己永远不能获取的锁。

    //悲观锁
    // 悲观锁，就是总是假设最坏的情况，每次去拿数据的时候都认为别人会修改，
    // 所以每次在拿数据的时候都会上锁，这样别人想拿这个数据就会阻塞。
    // 传统的关系型数据库里边就用到了很多这种锁机制，比如行锁，表锁等，读锁，写锁等，
    // 都是在做操作之前先上锁。synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
    //
    //乐观锁
    // 乐观锁，就是总是假设最好的情况，每次去拿数据的时候都认为别人不会修改，
    // 所以不会上锁，但是在更新的时候会判断一下在此期间有没有别人去更新这个数据。
    //
    // (暂时不懂：可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型，这样可以提高吞吐量，
    // 像数据库提供的类似于write_condition机制，其实都是提供的乐观锁。
    // 在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁
    // 的一种实现方式CAS实现的)
    //
    //
    //锁饥饿
    // 在非公平锁中，由于锁的分配机制是随机的。在锁的等待队伍中，就有可能有人运气不好，
    // 一直等不到锁，这就是锁饥饿。
    // 例如在悲观读写锁ReentrantReadWriteLock中，读读是不会互斥的，而读写是会互斥的。
    // 如果有大量的读线程和少量的写线程，就可能会出现大量的读线程前赴后继的占领着锁，
    // 而写的线程一直在等待。
    //
    //


    //----------------------------------------------------------------------------
    //synchronized的局限性：
    // 使用synchronized对方法加锁后，当A线程在执行该方法时别的线程只能在锁外等待，
    // 如果A线程拿到锁后在里面进行sleep阻塞，别的线程没有拿到锁也无法中断它，
    // 只能漫无目标的等下去，影响执行效率。因此需要有一种机制可以不让等待的无期限地
    // 进行下去，比如说只等待一定的时间 或 能够响应中断A线程的阻塞，Lock就可以做到。
    //

    //Lock 与 synchronized 对比
    // 1,synchronized是Java语言的关键字，
    //   Lock是一个接口(常用实现类ReentrantLock())，通过这个类可以实现同步访问。
    // 2,synchronized不需要用户去手动释放锁，线程执行完代码后会自动释放锁，
    //   Lock则必须要用户去手动释放锁，如果没有主动释放锁，就有可能导致出现死锁现象。


    //常用方法
    //方法一：void lock(); 上锁
    //方法二：void unlock(); 释放锁
    //方法三：boolean b = tryLock(); 尝试上锁，常与if配合使用
    //方法四：boolean b = tryLock(long time, TimeUnit unit);
    //方法五：void lockInterruptibly();



    /*

    《lockInterruptibly()》
    这个方法的作用就是去获取锁，如果这个锁当前是可以获得的，那么这个方法会立刻返回，
    但是如果这个锁当前是不能获得的（被其他线程持有），那么当前线程便会开始等待，
    除非它等到了这把锁或者是在等待的过程中被中断了，否则这个线程便会一直在这里执行这行代码。
    一句话总结就是，除非当前线程在获取锁期间被中断，否则便会一直尝试获取直到获取到为止顾名思义，
    lockInterruptibly() 是可以响应中断的。相比于不能响应中断的 synchronized 锁，
    lockInterruptibly() 可以让程序更灵活，可以在获取锁的同时，保持对中断的响应。
    我们可以把这个方法理解为超时时间是无穷长的 tryLock(long time, TimeUnit unit)，
    因为 tryLock(long time, TimeUnit unit) 和 lockInterruptibly() 都能响应中断，
    只不过 lockInterruptibly() 永远不会超时。这个方法本身是会抛出 InterruptedException 的，
    所以使用的时候，如果不在方法签名声明抛出该异常，那么就要写两个 try 块，如下所示

    在这个方法中我们首先执行了 lockInterruptibly 方法，并且对它进行了 try catch 包装，
    然后同样假设我们能够获取到这把锁，和之前一样，就必须要使用 try finall 来保障锁的绝对释放

    《unlock》
    unlock() 方法，是用于解锁的，unlock()方法比较简单，对于 ReentrantLock 而言，
    执行 unlock() 的时候，内部会把锁的“被持有计数器”减 1，直到减到 0 就代表当前这把锁已经完全释放了，
    如果减 1 后计数器不为 0，说明这把锁之前被“重入”了，那么锁并没有真正释放，仅仅是减少了持有的次数
--------------------------
《Lock》
    对于 Lock 接口而言，获取锁和释放锁都是显式的，不像 synchronized 那样是隐式的，
    所以 Lock 不会像 synchronized 一样在异常时自动释放锁（synchronized 即使不写
    对应的代码也可以释放），lock 的加锁和释放锁都必须以代码的形式写出来，所以使用 lock()
    时必须由我们自己主动去释放锁，因此最佳实践是执行 lock() 后，首先在 try{} 中操作同步资源，
    如果有必要就用 catch{} 块捕获异常，然后在 finally{} 中释放锁，以保证发生异常时锁一定被释放

    一定不要忘记在 finally 中添加 unlock() 方法，以便保障锁的绝对释放如果我们不遵守在
    finally 里释放锁的规范，就会让 Lock 变得非常危险，因为不知道未来什么时候由于异常的发生，
    导致跳过了 unlock() 语句，使得这个锁永远不能被释放了，其他线程也无法再获得这个锁，
    这就是 Lock 相比于 synchronized 的一个劣势，使用 synchronized 时不需要担心这个问题
    与此同时，lock() 方法不能被中断，这会带来很大的隐患：一旦陷入死锁，lock() 就会陷入永久等待，
    所以一般我们用 tryLock() 等其他更高级的方法来代替 lock()，下面我们就看一看 tryLock() 方法

    《tryLock()》
    tryLock() 用来尝试获取锁，如果当前锁没有被其他线程占用，则获取成功，返回 true，否则返回 false，
    代表获取锁失败。相比于 lock()，这样的方法显然功能更强大，可以根据是否能获取到锁来决定后续程序的
    行为因为该方法会立即返回，即便在拿不到锁时也不会一直等待，所以通常情况下，
    我们用 if 语句判断 tryLock() 的返回结果，根据是否获取到锁来执行不同的业务逻辑，典型使用方法如下

    创建 lock() 方法之后使用 tryLock() 方法并用 if 语句判断它的结果，如果 if 语句返回 true，
    就使用 try finally 完成相关业务逻辑的处理，如果 if 语句返回 false 就会进入 else 语句，
    代表它暂时不能获取到锁，可以先去做一些其他事情，比如等待几秒钟后重试，或者跳过这个任务，
    有了这个强大的 tryLock() 方法我们便可以解决死锁问题，代码如下所示

    如果代码中不用 tryLock() 方法，那么便可能会产生死锁，比如有两个线程同时调用这个方法，
    传入的 lock1 和 lock2 恰好是相反的，那么如果第一个线程获取了 lock1 的同时，
    第二个线程获取了 lock2，它们接下来便会尝试获取对方持有的那把锁，但是又获取不到，
    于是便会陷入死锁，但是有了 tryLock() 方法之后，我们便可以避免死锁的发生，
    首先会检测 lock1 是否能获取到，如果能获取到再尝试获取 lock2，但如果 lock1 获取不到也没有关系，
    我们会在下面进行随机时间的等待，这个等待的目标是争取让其他的线程在这段时间完成它的任务，
    以便释放其他线程所持有的锁，以便后续供我们使用，同理如果获取到了 lock1 但没有获取到 lock2，
    那么也会释放掉 lock1，随即进行随机的等待，只有当它同时获取到 lock1 和 lock2 的时候，
    才会进入到里面执行业务逻辑，比如在这里我们会打印出“获取到了两把锁，完成业务逻辑”，然后方法便会返回

    《tryLock(long time, TimeUnit unit)》
    tryLock() 的重载方法是 tryLock(long time, TimeUnit unit)，
    这个方法和 tryLock() 很类似，区别在于 tryLock(long time, TimeUnit unit)
    方法会有一个超时时间，在拿不到锁时会等待一定的时间，如果在时间期限结束后，
    还获取不到锁，就会返回 false；如果一开始就获取锁或者等待期间内获取到锁，则返回 true

     */
}
